[Javascript] - 자바스크립트 파일을 비동기로 불러오기

Javascript / December 18, 2020

아마 처음 HTML을 배우신 분들 중에서는 자바스크립트 파일을 추가할 때 <body> 태그를 닫기 전에 추가하라고 배운 분들도 계실 것입니다.
물론 절대 틀린 방법은 아니지만, 브라우저는 HTML 코드를 위에서 아래로 읽어나가기 때문에 이렇게 파일을 불러오면 약간의 시간 손해를 보게 됩니다.

// app.js
let div = document.getElementById("main")
let dummies = document.querySelectorAll(".dummies")
setTimeout(() => {
  div.innerHTML = "Hello, Chanmin!"
  for (dummy of dummies) {
    dummy.innerHTML = ""
  }
}, 5000)
<!-- index.html -->
<html lang="ko">
  <head>
    <meta charset="UTF-8" />
    <title>Test</title>
  </head>
  <body>
    <div id="main">Hello, world!</div>
    <div class="dummies">I am dummy!</div>
    <div class="dummies">I am dummy!</div>
    <div class="dummies">I am dummy!</div>
    <script src="./app.js"></script>
  </body>
</html>

예를 들면 브라우저는 위 코드를 해석하면서 <script> 태그를 만나는 순간 자바스크립트 파일을 불러온 후 실행하는데요, 따라서 스크립트 파일을 실행하는데 걸리는 시간은 HTML 파싱 시간 + 스크립트 로딩 시간 + 스크립트 실행 시간이 됩니다.

따라서 지금처럼 HTML이 짧은 경우라면 몰라도 프로젝트가 커지면서 HTML과 스크립트 파일의 용량이 커지면 결국 스크립트를 로딩하기까지 더 많은 시간이 낭비되고, 더군다나 웹 서버에 배포될 경우에는 이보다 더 많은 시간을 낭비하게 됩니다.

<!-- index.html -->
<html lang="ko">
  <head>
    <meta charset="UTF-8" />
    <title>Test</title>
    <script src="./app.js"></script>
  </head>
  <body>
    <div id="main">Hello, world!</div>
    <div class="dummies">I am dummy!</div>
    <div class="dummies">I am dummy!</div>
    <div class="dummies">I am dummy!</div>
  </body>
</html>

그럼 이렇게 <head> 태그 안에서 스크립트를 불러오면 어떨까요?
브라우저는 스크립트 태그를 더 일찍 불러와 읽기 시작하겠지만 아쉽게도 스크립트가 실행되는 시점이 <body> 태그의 요소들을 읽기 전이기 때문에 오류가 출력됩니다.

// app.js
let div = document.getElementById("main")
let dummies = document.querySelectorAll(".dummies")
setTimeout(() => {
  // HTML을 읽기 전에 스크립트를 실행했으므로 오류가 발생합니다.
  // Uncaught TypeError: Cannot set property 'innerHTML' of null
  div.innerHTML = "Hello, Chanmin!"
  for (dummy of dummies) {
    dummy.innerHTML = ""
  }
}, 5000)

다행히도 HTML을 개발할 때 이런 고민도 염두에 두었던 건지 스크립트 파일을 더 멋지게 불러오는 방법이 존재합니다.

1. defer 속성을 통해 자바스크립트 파일 불러오기

첫 번째 방법은 스크립트 태그의 defer 속성입니다.
‘미루다’ 라는 단어의 뜻처럼 defer 속성을 사용하면 스크립트 실행을 뒤로 미루게 되는데요, 여기서 엄청난 일이 벌어집니다.

<!-- index.html -->
<html lang="ko">
  <head>
    <meta charset="UTF-8" />
    <title>Test</title>
    <script src="./app.js" defer></script>
  </head>
  <body>
    <div id="main">Hello, world!</div>
    <div class="dummies">I am dummy!</div>
    <div class="dummies">I am dummy!</div>
    <div class="dummies">I am dummy!</div>
  </body>
</html>

바로 이전에 보았던 것처럼 <head> 영역에서 스크립트를 불러와 봅시다.
<body> 부분의 HTML은 아직 읽지 않았으니 원래대로면 오류가 출력되는 코드지만, 여기서 defer 가 마법을 부리기 시작합니다.

브라우저가 HTML을 해석하는 중 defer 속성을 가진 스크립트 태그를 만나면 스크립트를 바로 불러오기 시작하지만 실제 실행은 HTML을 다 읽은 후에 이루어집니다.
따라서 실제 스크립트의 실행 시간은 max(HTML 파싱 시간, 스크립트 로딩 시간) + 스크립트 실행 시간 이 되는데요, 이러면 시간적으로 이득일 뿐만 아니라 <body> 내에서 실제 마크업만을 신경쓸 수 있게 된다는 장점도 있습니다.

2. async 속성을 통해 자바스크립트 파일 불러오기

defer 처럼 비동기적으로 자바스크립트 파일을 불러오지만 동작 방식이 다른 async 라는 속성도 있습니다.

<!-- index.html -->
<html lang="ko">
  <head>
    <meta charset="UTF-8" />
    <title>Test</title>
    <script src="./app.js" async></script>
  </head>
  <body>
    <div id="main">Hello, world!</div>
    <div class="dummies">I am dummy!</div>
    <div class="dummies">I am dummy!</div>
    <div class="dummies">I am dummy!</div>
  </body>
</html>

async 속성은 defer와 마찬가지로 스크립트를 만나자마자 불러오기 시작하지만, 스크립트의 로딩이 끝난 즉시 실행된다는 점이 다릅니다.
따라서 브라우저가 HTML을 모두 읽기 전에 스크립트가 실행될 수 있기 때문에 DOM을 조작하는 코드에는 부적합하지만 날짜나 시간 데이터를 불러오는 등 백그라운드 로직의 빠른 수행이 필요할 때 로딩 시간을 단축할 수 있습니다.